use std::fs::{self, File};

use crate::config::base_conf::InitConfig;
use sea_orm::{ConnectOptions, ConnectionTrait, Database, DatabaseConnection};

use super::base_conf::Conf;

const DB_PATH: &'static str = "dbdata/local.db";
const DB_SQL_FILE_PATH: &'static str = "dbdata/local.db.sql";
const DB_UPDATE_SQL_FILE_PATH: &'static str = "dbdata/update.sql";

#[derive(Debug, Clone)]
struct DBConfig {
    db: Option<DatabaseConnection>,
    initable: bool, // be able to init db
}

impl InitConfig for DBConfig {
    fn init_config() -> Self {
        DBConfig {
            db: None,
            initable: false,
        }
    }
}

// using Conf::new()
static mut DBCONFIG: Conf<DBConfig> = Conf::<DBConfig>::new();

impl DBConfig {
    fn get_db(&self) -> &'static DatabaseConnection {
        unsafe {
            let db_wrapper = &self.db;
            if let Some(db) = db_wrapper {
                let x: *const DatabaseConnection = db as *const DatabaseConnection;

                &*x
            } else {
                panic!("db init failed or not init")
            }
        }
    }
}

pub async fn init_db() {
    let local_db = init_local_db().await;

    unsafe {
        DBCONFIG.set_struct(DBConfig {
            db: Some(local_db),
            initable: true,
        })
    }
}

// use unwrap() to release my pain
async fn init_local_db() -> DatabaseConnection {
    let mut _path = std::env::current_dir().unwrap();
    _path.push(DB_PATH);

    let mut need_create_table = false;

    if !_path.exists() {
        let _ = File::create(&_path).unwrap();
        need_create_table = true;
    }

    let opt_helper = String::from("sqlite:") + _path.to_str().unwrap() + "?mode=rwc";
    let sqlite_opt = ConnectOptions::new(opt_helper);

    let result_dbconn = Database::connect(sqlite_opt).await.unwrap();

    if need_create_table {
        let mut _path = std::env::current_dir().unwrap();
        _path.push(DB_SQL_FILE_PATH);

        let result = match tokio::fs::read_to_string(_path.clone()).await {
            Ok(re) => re,
            Err(e) => {
                tracing::error!("read sql file failed: {}", e);
                panic!("read sql file failed")
            }
        };

        result_dbconn
            .execute_unprepared(result.as_str())
            .await
            .unwrap_or_else(|err| {
                tracing::info!("sql file execute failed");
                match fs::remove_file(_path) {
                    Ok(()) => tracing::info!("file delete works"),
                    Err(e) => tracing::error!("delete sql file failed: {}", e),
                }

                panic!("sql file execute all failed")
            });

        tracing::info!("sql file execute all works");
    } else {
        // update sql file
        let mut _path = std::env::current_dir().unwrap();
        _path.push(DB_UPDATE_SQL_FILE_PATH);

        let result = match tokio::fs::read_to_string(_path.clone()).await {
            Ok(re) => re,
            Err(e) => {
                "".to_string() // fixed to empty string
            }
        };

        if !result.is_empty() {
            match result_dbconn.execute_unprepared(result.as_str()).await {
                Ok(_) => {}
                Err(_) => {}
            };

            match fs::remove_file(_path) {
                Ok(()) => tracing::info!("update sql file delete works"),
                Err(e) => tracing::error!("delete update sql file failed: {}", e),
            }

            tracing::info!("update sql file execute all works");
        }
    }

    result_dbconn
}

pub fn get_db() -> Result<&'static DatabaseConnection, String> {
    let db_config = unsafe { DBCONFIG.get_struct() };

    if !db_config.initable {
        return Err("db init failed or not init, may try init_db(url) in your code".to_owned());
    }

    let get_db = db_config.get_db();

    Ok(get_db)
}

pub fn close_db() {
    unsafe {
        let db_config = DBCONFIG.get_struct_into();
        tokio::spawn(db_config.db.unwrap().close());
    }
}

// factor as no unwrap and no panic, awesome
// async fn init_local_db() -> DatabaseConnection {
//     let path_wrapper = std::env::current_dir();

//     match path_wrapper {
//         Ok(mut path) => {
//             path.push("dbdata/local.db");

//             let mut need_create_table = false;
//             if path.exists() {
//                 match File::create(&path) {
//                     Ok(_) => {}
//                     Err(e) => {}
//                 }

//                 need_create_table = true;
//             }

//             match path.to_str() {
//                 Some(pathstr) => {
//                     let x = String::from("sqlite:") + pathstr + "?mode=rwc";

//                     let sqlite_opt = ConnectOptions::new(x);

//                     // the db wrapper to return
//                     let result_db = Database::connect(sqlite_opt).await;

//                     // condi start
//                     if need_create_table {
//                         let mut _path_wrap = std::env::current_dir();
//                         match _path_wrap {
//                             Ok(mut _path) => {
//                                 _path.push("dbdata/local.db.sql");
//                                 let res = match tokio::fs::read_to_string(_path.clone()).await {
//                                     Ok(re) => re,
//                                     Err(e) => {
//                                         panic!("failed")
//                                     }
//                                 };

//                                 match result_db {
//                                     // Dont move the result_db ownership
//                                     Ok(ref dbres) => {
//                                         match dbres.execute_unprepared(res.as_str()).await {
//                                             Ok(_) => {}
//                                             Err(e) => {}
//                                         }
//                                     }
//                                     Err(_) => {}
//                                 }
//                             }
//                             Err(e) => {}
//                         }
//                     } else {
//                         let mut _path_wrap = std::env::current_dir();
//                         match _path_wrap {
//                             Ok(mut _path) => {
//                                 _path.push("dbdata/update.sql");
//                                 let res = match tokio::fs::read_to_string(_path.clone()).await {
//                                     Ok(re) => re,
//                                     Err(_) => "".to_string(),
//                                 };

//                                 if !res.is_empty() {
//                                     match result_db {
//                                         Ok(ref dbres) => {
//                                             match dbres.execute_unprepared(res.as_str()).await {
//                                                 Ok(_) => {}
//                                                 Err(_) => {}
//                                             }
//                                         }
//                                         Err(_) => {}
//                                     }
//                                 }
//                             }
//                             Err(e) => {}
//                         }
//                     }

//                     match result_db {
//                         Ok(dbres) => dbres,

//                         Err(_) => DatabaseConnection::Disconnected,
//                     }
//                 }
//                 // Dont panic
//                 None => DatabaseConnection::Disconnected,
//             }
//         }

//         // for path_wrapper error, Dont panic
//         Err(e) => DatabaseConnection::Disconnected,
//     }
// }
